package com.easipass.unifiedconfig.unic;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.easipass.unifiedconfig.InsufficientPrivillageException;
import com.easipass.unifiedconfig.NotFoundException;
import com.easipass.unifiedconfig.Parameter;
import com.easipass.unifiedconfig.ParametersTraverser;
import com.easipass.unifiedconfig.UCI;
import com.easipass.unifiedconfig.NotFoundException.Part;
import com.easipass.unifiedconfig.Parameter.AccessLevel;
import com.easipass.unifiedconfig.core.Authentication;
import com.easipass.unifiedconfig.core.ConfigDomain;

/**
 * 
 * @version $Id$
 * @author itea
 * @date 2010-1-19
 */
public class UnicConfigDomain implements ConfigDomain{

	protected final String domain;
	
	protected final DomainRepo domainRepo;
	
	protected AuthManager authManager;
	
	protected Persistence persistence;
	
	protected final Object traverseLock = new Object();
	
	public UnicConfigDomain(DomainRepo domainRepo) {
		this.domain = domainRepo.getDomain();
		this.domainRepo = domainRepo;
	}
	
	public Parameter getParameter(UCI uci)
	throws NotFoundException, InsufficientPrivillageException {
		return getParameter(authManager.anonymous, uci);
	}
	
	public Parameter getParameter(Authentication a, UCI uci)
	throws NotFoundException, InsufficientPrivillageException {
		if(uci.getAccount() == null) throw new NullPointerException("user is blank");
		if(uci.getParameterName() == null) throw new NullPointerException("parameterName is blank");
		authManager.checkAuthentication(a);
		
		Parameter p = domainRepo.getParameter(uci);
		checkAuthorizedRead(a, p);
		return p.clone(); // CLONE
	}
	
	public void setParameter(Authentication a, Parameter p)
	throws NotFoundException, InsufficientPrivillageException {
		Parameter pp = p.clone();
		authManager.checkAuthentication(a);
		checkAuthorizedModify(a, pp.getUci());
		
		domainRepo.setParameter(pp); // CLONE
		persistent(pp);
	}
	
	public void removeParameter(Authentication a, UCI uci)
			throws NotFoundException, InsufficientPrivillageException {
		authManager.checkAuthentication(a);
		checkAuthorizedModify(a, uci);
		
		domainRepo.removeParameter(uci);
	}

	public void deleteParameterValues(Authentication a, UCI uci, String... values)
			throws NotFoundException, InsufficientPrivillageException {
		authManager.checkAuthentication(a);
		checkAuthorizedModify(a, uci);
		
		Parameter p = domainRepo.getParameter(uci);
		p.deleteValues(values);
	}

	public void insertParameterValues(Authentication a, UCI uci, String... values)
			throws NotFoundException, InsufficientPrivillageException {
		authManager.checkAuthentication(a);
		checkAuthorizedModify(a, uci);
		
		Parameter p = domainRepo.getParameter(uci);
		p.insertValues(values);
	}

	public boolean containsParameter(UCI uci) {
		return domainRepo.containsParameter(uci);
	}
	
	public List<UCI> listParameters(UCI range) {
		return domainRepo.listParameters(range);
	}

	protected void checkAuthorizedRead(Authentication a, Parameter p)
	throws InsufficientPrivillageException {
		authManager.checkAuthentication(a);
		
		if(!isAuthorizedRead(a, p))
			throw new InsufficientPrivillageException(p.getUci());
	}
	protected void checkAuthorizedModify(Authentication a, UCI u)
	throws InsufficientPrivillageException {
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedModify(a, u))
			throw new InsufficientPrivillageException(u);
	}
	protected boolean isAuthorizedRead(Authentication a, Parameter p) {
		if(a == null) return false;
		authManager.checkAuthentication(a);
		
		if(AccessLevel.PUBLIC == p.getAccessLevel())
			return true; // PUBLIC parameter always readable by any one.
		return authManager.isAuthorizedModify(a, p.getUci());
	}
	public void createAccount(Authentication a, String account)
	throws InsufficientPrivillageException {
		if((account = trimNull(account)) == null) throw new NullPointerException("account");
		authManager.checkAuthentication(a);
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createaccount"))
			throw new InsufficientPrivillageException("insufficient privillage to create account");
		
		domainRepo.createAccountRepo(account);
	}

	public void createModule(Authentication a, String account, String module)
	throws NotFoundException, InsufficientPrivillageException {
		if((account = trimNull(account)) == null) throw new NullPointerException("account");
		if((module = trimNull(module)) == null) throw new NullPointerException("module");
		authManager.checkAuthentication(a);
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createmodule"))
			throw new InsufficientPrivillageException("insufficient privillage to create module");
		
		AccountRepo ar = domainRepo.getAccountRepo(account);
		if(ar == null)
			throw new NotFoundException(Part.ACCOUNT, account);
		ar.createModuleRepo(trimNull(module));
	}

	public void removeAccount(Authentication a, String account)
			throws InsufficientPrivillageException {
		if((account = trimNull(account)) == null) throw new NullPointerException("account");
		authManager.checkAuthentication(a);
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createaccount"))
			throw new InsufficientPrivillageException("insufficient privillage to create account");
		
		domainRepo.removeAccountRepo(account);
	}

	public boolean existsAccount(Authentication a, String account) {
		return domainRepo.getAccountRepo(account) != null;
	}

	public boolean existsModule(Authentication a, String account, String module) {
		AccountRepo ar = domainRepo.getAccountRepo(account);
		if(ar == null) return false;
		return ar.getModuleRepo(module) != null;
	}

	public void removeModule(Authentication a, String account, String module)
			throws NotFoundException, InsufficientPrivillageException {
		if((account = trimNull(account)) == null) throw new NullPointerException("account");
		if((module = trimNull(module)) == null) throw new NullPointerException("module");
		authManager.checkAuthentication(a);
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createmodule"))
			throw new InsufficientPrivillageException("insufficient privillage to create module");
		
		AccountRepo ar = domainRepo.getAccountRepo(account);
		if(ar == null)
			throw new NotFoundException(Part.ACCOUNT, account);
		ar.removeModuleRepo(trimNull(module));
	}

	public List<UCI> listAccounts(Authentication a) {
		Set<String> aSet = domainRepo.accountIdx.keySet();
		List<UCI> ucis = new ArrayList<UCI>(aSet.size());
		for(String acc : aSet)
			ucis.add(new UCI(domain, acc, null, null));
		return ucis;
	}

	public List<UCI> listModules(Authentication a, String account)
	throws NotFoundException {
		Collection<AccountRepo> ars = new ArrayList<AccountRepo>();
		if("*".equals(account)) {
			ars = domainRepo.accountIdx.values();
		} else {
			AccountRepo ar = domainRepo.getAccountRepo(account);
			if(ar == null)
				throw new NotFoundException(Part.ACCOUNT, account);
			ars.add(ar);
		}
		
		LinkedList<UCI> ucis = new LinkedList<UCI>();
		for(AccountRepo ar : ars) {
			Set<String> aSet = ar.moduleIdx.keySet();
			for(String mo : aSet)
				ucis.add(new UCI(domain, ar.getAccount(), mo, null));
		}
		return ucis;
	}

	// ----
	
	public Authentication authenticate(String user, String password) {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		if(password == null) throw new NullPointerException("password is null");
		
		return authManager.authenticate(user, password);
	}

	public boolean isLegalAuthentication(Authentication a) {
		return authManager.isAuthenticated(a);
	}

	public boolean createUser(Authentication a, String user, String password)
	throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		if(password == null) throw new NullPointerException("password is null");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createuser"))
			throw new InsufficientPrivillageException("insufficient privillage to create user");
		if(authManager.existsUser(u))
			return false;
		authManager.setUserPassword(u, password);
		return true;
	}
	
	public boolean changeUserPassword(Authentication a, String user,
			String newPassword) throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		if(newPassword == null) throw new NullPointerException("password is null");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createuser"))
			throw new InsufficientPrivillageException("insufficient privillage to create user");
		if(!authManager.existsUser(u))
			return false;
		authManager.setUserPassword(u, newPassword);
		return true;
	}

	public boolean existsUser(String user) {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		return authManager.existsUser(u);
	}

	public List<String> listUsers() {
		return authManager.listUsers();
	}

	public boolean removeUser(Authentication a, String user)
			throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "createuser"))
			throw new InsufficientPrivillageException("insufficient privillage to create user");
		if(!authManager.existsUser(u))
			return false;
		authManager.setUserPassword(u, null);
		return true;
	}

	// ----
	
	public void grantModify(Authentication a, String user, UCI uci)
	throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "grantmodify"))
			throw new InsufficientPrivillageException("insufficient privillage to grant modify");
		authManager.grantModify(u, uci);
	}
	
	public void revokeModify(Authentication a, String user, UCI uci)
	throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "grantmodify"))
			throw new InsufficientPrivillageException("insufficient privillage to revoke modify");
		authManager.revokeModify(u, uci);
	}
	
	public void grantSys(Authentication a, String user, String pri)
			throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		String p = trimNull(pri);
		if(p == null) throw new NullPointerException("privillage is blank");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "grantsys"))
			throw new InsufficientPrivillageException("insufficient privillage to grant sys");
		authManager.grantSys(u, p);
	}

	public void revokeSys(Authentication a, String user, String pri)
			throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		String p = trimNull(pri);
		if(p == null) throw new NullPointerException("privillage is blank");
		authManager.checkAuthentication(a);
		
		if(!authManager.isAuthorizedSysPrivillage(a.getUser(), "grantsys"))
			throw new InsufficientPrivillageException("insufficient privillage to revoke sys");
		authManager.revokeSys(u, p);
	}

	public List<String> listModifyPrivillages(Authentication a, String user)
			throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		String[] us = authManager.listModifyPrivillages(u);
		List<String> ls = new ArrayList<String>(us.length);
		for(String s: us)
			ls.add(s);
		return ls;
	}

	public List<String> listSysPrivillages(Authentication a, String user)
			throws InsufficientPrivillageException {
		String u = trimNull(user);
		if(u == null) throw new NullPointerException("user is blank");
		String[] us = authManager.listModifyPrivillages(u);
		List<String> ls = new ArrayList<String>(us.length);
		for(String s: us)
			ls.add(s);
		return ls;
	}

	public void traverseParameters(Authentication a, UCI u,
			ParametersTraverser traverser) {
	synchronized(traverseLock) {
		authManager.checkAuthentication(a);
		
		if(!u.matchesDomain(domain)) return;
		
		traverser.beginTraverse(domain);
		domainRepo.traverseParameters(a, u, traverser);
		traverser.endTravese(domain);
	}
	}

	private void persistent(Parameter p) {
		if(persistence != null)
			try {
				persistence.save(p);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	protected static String trimNull(String s) {
		if(s == null) return null;
		return (s = s.trim()).length() == 0 ? null : s;
	}

	public String getDomain() {
		return domain;
	}

	protected void setAuthManager(AuthManager authManager) {
		this.authManager = authManager;
	}

	public void setPersistence(Persistence persistence) {
		this.persistence = persistence;
	}

}
